﻿using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Threading;
using log4net;
using Needle.Bus.Engine.Utils;
using Needle.Utils.Collections;

namespace Needle.Bus.Engine.Schedulers
{
    public class Scheduler : EngineComponent, IScheduler<Scheduler>
    {

        private readonly ILog _log;
        private readonly StageInfoCollection _stageInfo;

        #region constructors

        public Scheduler(string id)
            : this(new NamedMutex(id), id) { }

        public Scheduler(object mutex, string id)
            : base(mutex, id)
        {
            _log = LogManager.GetLogger(GetType().Name + " " + id);
            _stageInfo = new StageInfoCollection();
            _extensions = new ExtensionCollection<Scheduler>(this, ThisLock);
        }

        #endregion

        private readonly IExtensionCollection<Scheduler> _extensions;
        public IExtensionCollection<Scheduler> Extensions
        {
            get { return _extensions; }
        }

        public void Register(IStage stage)
        {
            if (stage == null)
                throw new ArgumentNullException("stage", "stage is null.");
            lock (ThisLock)
            {
                ThrowIfDisposed();
                if (_stageInfo.Exists(runner => runner.Stage == stage
                                    || runner.Stage.Id == stage.Id))
                    throw new ArgumentException(String.Format("Stage {0} or a stage with the same id already registered", stage.Id));
                _stageInfo.Add(new StageInfo(stage.Id, stage, new StagePump(stage)));
                /* simple solution */

            }
        }

        public void Unregister(IStage stage)
        {
            lock (ThisLock)
            {
                _stageInfo.RemoveAll(info => info.Stage == stage);
            }
        }

        Random rnd = new Random(Environment.TickCount);
        public void WorkerIdle(Worker<Scheduler> worker)
        {
            /* Don't do nuthin' if we're closing or closed. */
            if (State == CommunicationState.Opened
                && worker.State == CommunicationState.Opened)
            {
                _log.DebugFormat("worker {0} idle.", worker.Id);
                IRichList<StageInfo> infos = null;
                int delay;
                lock (ThisLock)
                {
                    delay = rnd.Next(20, 100);

                    infos = _stageInfo.FindAll(x =>
                    {
                        int minBatchSize;
                        var batchMode = x.Stage.BatchMode;
                        if (batchMode.Enabled)
                            minBatchSize = batchMode.MinBatchSize;
                        else
                            minBatchSize = 1;
                        return !x.Stage.IsActive && x.Stage.Buffer.Count >= minBatchSize;
                    });
                }
                if (infos.Count != 0)
                {
                    foreach (var info in infos)
                        worker.Work.TryEnqueue(info.Runner);
                    return;
                }
                /* TODO: find something more intelligent to do, 
                 * like waiting on messages enqueues. 
                 * Also, this is a nice spot to do worker management. */
                Thread.Sleep(delay);
            }
        }

        #region privates

        protected override void OnOpen(TimeSpan timeout)
        {
            for (int i = 1; i <= 3; i++)
            {
                Worker w = new Worker(String.Format("{0}_Worker{1}", Id, i));
                Extensions.Add(w);
                w.Open();
            }

        }

        protected override void OnAbort()
        {
            foreach (Worker w in Extensions)
            {
                w.Abort();
            }
        }

        protected override void OnClose(TimeSpan timeout)
        {
            try
            {
                foreach (Worker w in Extensions)
                {
                    w.Close();
                }
            }
            finally
            {
                lock (ThisLock)
                {
                    _stageInfo.Clear();
                }
            }
        }

        #endregion

        #region Worker

        private class Worker : Worker<Scheduler>
        {
            private ILog _log;
            private Scheduler _scheduler;

            public Worker(string id)
                : base(id)
            {
                _log = LogManager.GetLogger(id);
            }

            protected override void Run()
            {
                try
                {
                    _log.InfoFormat("Starting worker '{0}'", Id);
                    while (Running)
                    {
                        IWorkItem workItem = null;
                        if (Work.TryDequeue(out workItem))
                        {
                            workItem.Run();
                        }
                        else
                        {
                            _scheduler.WorkerIdle(this);
                        }
                    }
                    _log.InfoFormat("Stopped worker '{0}'", Id);
                }
                catch (Exception ex)
                {
                    _log.Error(String.Format("Worker '{0}' crashed.", Id), ex);
                }
            }

            public override void Attach(Scheduler owner)
            {
                lock (ThisLock)
                {
                    ThrowIfDisposedOrImmutable();
                    if (_scheduler != null)
                        throw new InvalidOperationException("Already attached to scheduler " + _scheduler.Id);
                    _scheduler = owner;

                }
            }

            public override void Detach(Scheduler owner)
            {
                lock (ThisLock)
                {
                    if (_scheduler == null || _scheduler != owner)
                        throw new InvalidOperationException("Cannot detach, attached to a different scheduler (" + owner.Id + ")");
                    _scheduler = null;
                }
            }

        }

        #endregion

        private class StageInfoCollection : RichCollection<StageInfo>
        {
            public StageInfoCollection() { }
        }

        private class StageInfo
        {

            public StageInfo(string id, IStage stage, StagePump runner)
            {
                _id = id;
                _stage = stage;
                _runner = runner;
            }

            private readonly string _id;
            public string Id
            {
                get { return _id; }
            }

            private readonly IStage _stage;
            public IStage Stage
            {
                get { return _stage; }
            }

            private readonly StagePump _runner;
            public StagePump Runner
            {
                get { return _runner; }
            }

        }

    }
}
